/*****************************************************************************
 * hal_stm32_spi.c
 *
 * Copyright (C) 2019 Jeasonvor <1101627719@qq.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ****************************************************************************/

/*****************************************************************************
 * Included Files
 ****************************************************************************/

#include "plum_api.h"
#include "hal/source/hal_stm32f1/hal_stm32f1.h"
/*****************************************************************************
 * Trace Definitions
 ****************************************************************************/

/*****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define HAL_STM32_SPI_MOSI_LOW(SPIx) \
    plum_hal_gpio_write(SPIx->spi_hdl.sspi.mosi_pin, plum_false)
#define HAL_STM32_SPI_MOSI_HIGH(SPIx) \
    plum_hal_gpio_write(SPIx->spi_hdl.sspi.mosi_pin, plum_true)
#define HAL_STM32_SPI_CLK_LOW(SPIx) \
    plum_hal_gpio_write(SPIx->spi_hdl.sspi.clk_pin, plum_false)
#define HAL_STM32_SPI_CLK_HIGH(SPIx) \
    plum_hal_gpio_write(SPIx->spi_hdl.sspi.clk_pin, plum_true)
#define HAL_STM32_SPI_MISO_READ(SPIx) \
    plum_hal_gpio_read(SPIx->spi_hdl.sspi.miso_pin)

/*****************************************************************************
 * Private Types
 ****************************************************************************/
struct stm32_spi;

struct spi_soft {
    plum_u32 clk_pin;
    plum_u32 mosi_pin;
    plum_u32 miso_pin;

    plum_u32 freq;
    plum_u8  word_width;

    void (*transfer_byte)(struct stm32_spi *spix, plum_u8 wData,
                          plum_u8 *rData);
};

union spi_handle {
    /* hardware spi */
    SPI_HandleTypeDef hspi;

    /* software spi */
    struct spi_soft sspi;
};

struct stm32_spi {
    /* hal SPI ID */
    plum_u32 id;

    /* SPI handler */
    union spi_handle spi_hdl;

    /* next spi handle */
    struct stm32_spi *next;
};

/*****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/*****************************************************************************
 * Private Data
 ****************************************************************************/

/* spi interface header */
PLUM_PRIVATE struct stm32_spi spi_hd;

/*****************************************************************************
 * Public Data
 ****************************************************************************/

/*****************************************************************************
 * Private Functions
 ****************************************************************************/

/*
 * @brief:  查询SPI句柄
 *
 * @param[in]:  id  -   SPI plum id
 *
 * @return: 返回SPI操作句柄
 **/
PLUM_PRIVATE struct stm32_spi *hal_stm32_spi_instance_select(plum_u32 id)
{
    struct stm32_spi *spi = spi_hd.next;

    while (spi) {
        if (spi->id == PLUM_HAL_SPI_IDX(id)) {
            break;
        }
        spi = spi->next;
    }

    return spi;
}

/*
 * @brief:  注册SPI句柄
 *
 * @param[in]:  id  -   SPI plum id
 *
 * @return: 返回SPI操作句柄
 **/
PLUM_PRIVATE struct stm32_spi *hal_stm32_spi_instance_register(plum_u32 id)
{
    struct stm32_spi *spi = plum_kal_mem_malloc(sizeof(struct stm32_spi));
    if (!spi) {
        return (NULL);
    }
    memset(spi, 0, sizeof(struct stm32_spi));
    spi->id = PLUM_HAL_SPI_IDX(id);

    switch (spi->id) {
#if defined(SPI1)
        case 1:
            spi->spi_hdl.hspi.Instance = SPI1;
            break;
#endif

#if defined(SPI2)
        case 2:
            spi->spi_hdl.hspi.Instance = SPI1;
            break;
#endif

#if defined(SPI3)
        case 3:
            spi->spi_hdl.hspi.Instance = SPI3;
            break;
#endif

        case 101:
        case 102:
            break;

        default:
            plum_kal_mem_malloc(spi);
            return (NULL);
    }

    struct stm32_spi *ptr = &spi_hd;
    do {
        if (!ptr->next) {
            ptr->next = spi;
        }
        else if (ptr->id == spi->id) {
            plum_kal_mem_malloc(spi);
            break;
        }
    } while (ptr->next);

    return (ptr);
}

PLUM_PRIVATE
plum_void hal_stm32_spi_soft_transfer_mode0(struct stm32_spi *spix,
                                            plum_u8 wData, plum_u8 *rData)
{
}

PLUM_PRIVATE
plum_void hal_stm32_spi_soft_transfer_mode1(struct stm32_spi *spix,
                                            plum_u8 wData, plum_u8 *rData)
{
}

PLUM_PRIVATE
plum_void hal_stm32_spi_soft_transfer_mode2(struct stm32_spi *spix,
                                            plum_u8 wData, plum_u8 *rData)
{
}

PLUM_PRIVATE
plum_void hal_stm32_spi_soft_transfer_mode3(struct stm32_spi *spix,
                                            plum_u8 wData, plum_u8 *rData)
{
    plum_u8 loop;
    for (loop = 0; loop < 8; loop++) {
        HAL_STM32_SPI_CLK_LOW(spix);

        HAL_STM32_SPI_CLK_HIGH(spix);

        *rData |= HAL_STM32_SPI_MISO_READ(spix) << (7 - loop);
    }
}

PLUM_PRIVATE plum_s32 hal_stm32_spi_soft_mode(struct stm32_spi *  spix,
                                              plum_hal_spi_mode_t mode)
{
    plum_s32 rc = PLUM_ECODE_OK;

    switch (mode) {
        case PLUM_HAL_SPI_MODE_0:
            HAL_STM32_SPI_CLK_LOW(spix);
            spix->spi_hdl.sspi.transfer_byte =
                hal_stm32_spi_soft_transfer_mode0;
            break;

        case PLUM_HAL_SPI_MODE_1:
            HAL_STM32_SPI_CLK_LOW(spix);
            spix->spi_hdl.sspi.transfer_byte =
                hal_stm32_spi_soft_transfer_mode1;
            break;

        case PLUM_HAL_SPI_MODE_2:
            HAL_STM32_SPI_CLK_HIGH(spix);
            spix->spi_hdl.sspi.transfer_byte =
                hal_stm32_spi_soft_transfer_mode2;
            break;

        case PLUM_HAL_SPI_MODE_3:
            HAL_STM32_SPI_CLK_HIGH(spix);
            spix->spi_hdl.sspi.transfer_byte =
                hal_stm32_spi_soft_transfer_mode3;
            break;

        default:
            rc = PLUM_ECODE_EPARA;
            break;
    }

    return (rc);
}

/*
 * @brief:  注册软件模拟spi
 *
 * @param[in]:  id      -   plum id
 * @param[in]:  config  -   配置指针
 *
 * @return: 返回执行状态
 **/
PLUM_PRIVATE plum_s32 hal_stm32_spi_soft_init(plum_u32            id,
                                              plum_hal_spi_cof_t *config)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!config) {
            rc = (PLUM_ECODE_EPARA);
            break;
        }

        /* register */
        struct stm32_spi *SPIx = hal_stm32_spi_instance_register(id);
        if (!SPIx) {
            rc = (PLUM_ECODE_EIO);
            break;
        }

        plum_hal_gpio_cof_t gpio;
        gpio.mode = PLUM_HAL_GPIO_MODE_COMMON;

        /* CLK */
        gpio.config.com = PLUM_HAL_GPIO_MODE_OUT_PP;
        rc              = plum_hal_gpio_init(config->pin.clk_pin, &gpio);
        if (rc) {
            break;
        }
        SPIx->spi_hdl.sspi.clk_pin = config->pin.clk_pin;

        /* MOSI */
        gpio.config.com = PLUM_HAL_GPIO_MODE_OUT_PP;
        rc              = plum_hal_gpio_init(config->pin.mosi_pin, &gpio);
        if (rc) {
            break;
        }
        SPIx->spi_hdl.sspi.mosi_pin = config->pin.mosi_pin;

        /* MISO */
        gpio.config.com = PLUM_HAL_GPIO_MODE_INPUT;
        rc              = plum_hal_gpio_init(config->pin.miso_pin, &gpio);
        if (rc) {
            break;
        }
        SPIx->spi_hdl.sspi.miso_pin = config->pin.miso_pin;

        /* mode */
        rc = hal_stm32_spi_soft_mode(SPIx, config->mode);
        if (rc) {
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PRIVATE plum_s32 hal_stm32_spi_hard_init(plum_u32            id,
                                              plum_hal_spi_cof_t *config)
{
    plum_s32 rc = PLUM_ECODE_OK;

    struct stm32_spi *SPIx = hal_stm32_spi_instance_register(id);
    if (!SPIx) {
        return (PLUM_ECODE_EIO);
    }

    if (!config) {
        return (PLUM_ECODE_EPARA);
    }

    // init
    do {
        SPI_HandleTypeDef *spi_handle = &SPIx->spi_hdl.hspi;

        spi_handle->Init.Mode           = SPI_MODE_MASTER;
        spi_handle->Init.FirstBit       = SPI_FIRSTBIT_LSB;
        spi_handle->Init.NSS            = SPI_NSS_SOFT;
        spi_handle->Init.Direction      = SPI_DIRECTION_2LINES;
        spi_handle->Init.TIMode         = SPI_TIMODE_DISABLE;
        spi_handle->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
        spi_handle->Init.CRCPolynomial  = 7;
#if 0
        spi_handle->Init.CRCLength      = SPI_CRC_LENGTH_DATASIZE;
        spi_handle->Init.NSSPMode       = SPI_NSS_PULSE_DISABLE;
#endif
        /* mode */
        if (config->mode == PLUM_HAL_SPI_MODE_0) {
            spi_handle->Init.CLKPolarity = SPI_POLARITY_LOW; /* CPOL = 0 */
            spi_handle->Init.CLKPhase    = SPI_PHASE_1EDGE;  /* CPHA = 0 */
        }
        else if (config->mode == PLUM_HAL_SPI_MODE_1) {
            spi_handle->Init.CLKPolarity = SPI_POLARITY_LOW; /* CPOL = 0 */
            spi_handle->Init.CLKPhase    = SPI_PHASE_2EDGE;  /* CPHA = 1 */
        }
        else if (config->mode == PLUM_HAL_SPI_MODE_2) {
            spi_handle->Init.CLKPolarity = SPI_POLARITY_HIGH; /* CPOL = 1 */
            spi_handle->Init.CLKPhase    = SPI_PHASE_1EDGE;   /* CPHA = 0 */
        }
        else if (config->mode == PLUM_HAL_SPI_MODE_3) {
            spi_handle->Init.CLKPolarity = SPI_POLARITY_HIGH; /* CPOL = 1 */
            spi_handle->Init.CLKPhase    = SPI_PHASE_2EDGE;   /* CPHA = 1 */
        }
        else {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        /*
         * freq
         * We assume APB1 = APB2, other word PCLK1 == PCLK2
         * stm32 chip: SPI may be mounted on different clocks,
         * but I don't want to make complicated distinctions.
         */
        plum_u32 SPI_APB_CLOCK, result_freq;
        SPI_APB_CLOCK = HAL_RCC_GetPCLK2Freq();
        if (config->freq >= SPI_APB_CLOCK / 2) {
            spi_handle->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
            result_freq                        = SPI_APB_CLOCK / 2;
        }
        else if (config->freq >= SPI_APB_CLOCK / 4) {
            spi_handle->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
            result_freq                        = SPI_APB_CLOCK / 4;
        }
        else if (config->freq >= SPI_APB_CLOCK / 8) {
            spi_handle->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
            result_freq                        = SPI_APB_CLOCK / 8;
        }
        else if (config->freq >= SPI_APB_CLOCK / 16) {
            spi_handle->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
            result_freq                        = SPI_APB_CLOCK / 16;
        }
        else if (config->freq >= SPI_APB_CLOCK / 32) {
            spi_handle->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
            result_freq                        = SPI_APB_CLOCK / 32;
        }
        else if (config->freq >= SPI_APB_CLOCK / 64) {
            spi_handle->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;
            result_freq                        = SPI_APB_CLOCK / 64;
        }
        else if (config->freq >= SPI_APB_CLOCK / 128) {
            spi_handle->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;
            result_freq                        = SPI_APB_CLOCK / 128;
        }
        else {
            /*  min prescaler 256 */
            spi_handle->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
            result_freq                        = SPI_APB_CLOCK / 256;
        }

        /* bits_per_word */
        if (config->word_width == 8) {
            spi_handle->Init.DataSize = SPI_DATASIZE_8BIT;
        }
        else if (config->word_width == 16) {
            spi_handle->Init.DataSize = SPI_DATASIZE_16BIT;
        }
        else {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        spi_handle->State = HAL_SPI_STATE_RESET;

        if (HAL_SPI_Init(spi_handle) != HAL_OK) {
            rc = (PLUM_ECODE_EIO);
        }

    } while (0);

    return (rc);
}

/*****************************************************************************
 * Public Functions
 ****************************************************************************/

PLUM_PUBLIC
plum_s32 plum_hal_spi_init(plum_u32 id, plum_hal_spi_cof_t *config)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!PLUM_HAL_ID_IS_SPI(id)) {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        if (!config) {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        if (HAL_STM32_SPI_IS_HARD(id)) {
            rc = hal_stm32_spi_hard_init(id, config);
        }
        else {
            rc = hal_stm32_spi_soft_init(id, config);
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_hal_spi_transfer(plum_u32 id, plum_u8 *rData, plum_u8 *wData,
                               plum_u32 len)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!PLUM_HAL_ID_IS_SPI(id)) {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        struct stm32_spi *SPIx = hal_stm32_spi_instance_select(id);
        if (!SPIx) {
            rc = (PLUM_ECODE_EIO);
            break;
        }

        if (HAL_STM32_SPI_IS_HARD(id)) {
            if (rData && wData) {
                rc = HAL_SPI_TransmitReceive(&SPIx->spi_hdl.hspi, wData, rData,
                                             len, HAL_MAX_DELAY);
                if (HAL_OK != (HAL_StatusTypeDef)rc) {
                }
            }
            else if (rData) {
                rc = HAL_SPI_Receive(&SPIx->spi_hdl.hspi, rData, len,
                                     HAL_MAX_DELAY);
                if (rc) {
                }
            }
            else if (wData) {
                rc = HAL_SPI_Transmit(&SPIx->spi_hdl.hspi, wData, len,
                                      HAL_MAX_DELAY);
                if (rc) {
                }
            }
        }
        else {
            plum_u32 loop;
            for (loop = 0; loop < len; loop++) {
                SPIx->spi_hdl.sspi.transfer_byte(SPIx, *wData, rData);
            }
        }
    } while (0);

    return (rc);
}

/****************************************************************************/
/*                                                                          */
/*  End of file.                                                            */
/*                                                                          */
/****************************************************************************/
